package top.bulk.encrypt.advice;

import cn.hutool.core.annotation.AnnotationUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.MethodParameter;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
import top.bulk.encrypt.annotation.Encrypt;
import top.bulk.encrypt.strategy.CryptoWrapper;
import top.bulk.encrypt.util.AnnUtil;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

/**
 * 加密 处理类
 * RequestBodyAdvice 是 Spring Framework 提供的一个接口，它允许开发者在控制器方法接收到请求体数据之前，对请求体的数据进行预处理。这个接口可以被用来实现诸如请求参数的日志记录、解密、验证签名等功能，尤其在需要对敏感数据进行加密传输的场景下非常有用
 *
 * @author 散装java
 * @date 2024-07-18
 */
@Component
@Slf4j
public class EncryptResponseBodyAdvice implements ResponseBodyAdvice<Object> {
    /**
     * 判断当前转换器是否支持
     *
     * @param returnType    方法返回类型
     * @param converterType 转换器类型
     * @return 是否支持
     */
    @Override
    public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
        // 获取当前方法的返回类型
        Class<?> clazz = returnType.getNestedParameterType();

        // 判断当前返回类型是否被 @Encrypt 注解
        return AnnUtil.hasAnnotation(clazz, Encrypt.class);
    }

    /**
     * 在控制器方法返回数据之前，对返回的数据进行预处理
     *
     * @param body                  即将被写入 HTTP 响应体的对象
     * @param returnType            当前请求的返回类型信息
     * @param selectedContentType   已选择的媒体类型，即响应的内容类型
     * @param selectedConverterType 已选择的转换器类型
     * @param request               代表当前的 HTTP 请求 request
     * @param response              代表当前的 HTTP 请求 response
     * @return obj
     */
    @Override
    public Object beforeBodyWrite(@Nullable Object body, MethodParameter returnType, MediaType selectedContentType, Class<? extends HttpMessageConverter<?>> selectedConverterType, ServerHttpRequest request, ServerHttpResponse response) {
        if (body == null) {
            return null;
        }
        response.getHeaders().setContentType(MediaType.TEXT_PLAIN);
        Method method = returnType.getMethod();
        if (method != null) {
            Class<?> returnTypeClass = method.getReturnType();
            Field[] fields = returnTypeClass.getDeclaredFields();
            // 获取所有的字段，对有加密需求的字段做处理
            for (Field field : fields) {
                // 获取注解信息
                Encrypt encrypt = AnnotationUtil.getAnnotation(field, Encrypt.class);

                if (encrypt != null) {
                    Object value = ReflectUtil.getFieldValue(body, field);
                    if (value != null) {
                        // 执行加密操作
                        String encryptResult = encrypt.strategy().encrypt(new CryptoWrapper().setFieldValue(value.toString()).setArguments(encrypt.arguments()));
                        // 通过反射更新数值
                        ReflectUtil.setFieldValue(body, field, encryptResult);
                    }
                }
            }

        }
        return body;
    }

}
